/*!
 * \file WtMinImpactExeUnit.cpp
 * \project	WonderTrader
 *
 * \author Wesley
 * \date 2020/03/30
 *
 * \brief 最小冲击执行单元实现
 * \details 实现最小市场冲击的执行算法，通过智能下单策略减少对市场的冲击
 */
#include "WtMinImpactExeUnit.h"

#include "../Includes/WTSVariant.hpp"
#include "../Includes/WTSContractInfo.hpp"
#include "../Includes/WTSSessionInfo.hpp"
#include "../Share/decimal.h"
#include "../Share/StrUtil.hpp"
#include "../Share/fmtlib.h"

extern const char* FACT_NAME;

/// 价格模式名称数组
const char* PriceModeNames[] =
{
	"BESTPX",		///< 对手价
	"LASTPX",		///< 最新价
	"MARKET",		///< 排队价
	"AUTOPX"		///< 自动价
};

/**
 * @brief 获取真实目标仓位
 * @param target 目标仓位值
 * @return 返回真实的目标仓位，DBL_MAX表示清仓，返回0
 */
inline double get_real_target(double target)
{
	if (target == DBL_MAX)
		return 0;

	return target;
}

/**
 * @brief 判断是否为清仓操作
 * @param target 目标仓位值
 * @return 是清仓操作返回true，否则返回false
 */
inline bool is_clear(double target)
{
	return (target == DBL_MAX);
}


/**
 * @brief 构造函数
 * @details 初始化所有成员变量为默认值
 */
WtMinImpactExeUnit::WtMinImpactExeUnit()
	: _last_tick(NULL)
	, _comm_info(NULL)
	, _price_mode(0)
	, _price_offset(0)
	, _expire_secs(0)
	, _cancel_cnt(0)
	, _target_pos(0)
	, _cancel_times(0)
	, _last_place_time(0)
	, _last_tick_time(0)
{
}


/**
 * @brief 析构函数
 * @details 释放持有的资源，包括tick数据和品种信息
 */
WtMinImpactExeUnit::~WtMinImpactExeUnit()
{
	if (_last_tick)
		_last_tick->release();

	if (_comm_info)
		_comm_info->release();
}

/**
 * @brief 获取执行工厂名称
 * @return 返回所属执行工厂的名称
 */
const char* WtMinImpactExeUnit::getFactName()
{
	return FACT_NAME;
}

/**
 * @brief 获取执行单元名称
 * @return 返回执行单元的名称
 */
const char* WtMinImpactExeUnit::getName()
{
	return "WtMinImpactExeUnit";
}

/**
 * @brief 初始化执行单元
 * @param ctx 执行上下文
 * @param stdCode 标准合约代码
 * @param cfg 配置参数
 * @details 从配置中读取执行参数，包括价格模式、偏移量、超时时间等
 */
void WtMinImpactExeUnit::init(ExecuteContext* ctx, const char* stdCode, WTSVariant* cfg)
{
	ExecuteUnit::init(ctx, stdCode, cfg);

	_comm_info = ctx->getCommodityInfo(stdCode);
	if (_comm_info)
		_comm_info->retain();

	_sess_info = ctx->getSessionInfo(stdCode);
	if (_sess_info)
		_sess_info->retain();

	_price_offset = cfg->getInt32("offset");	///< 价格偏移量，正数表示向上偏移，负数表示向下偏移
	_expire_secs = cfg->getUInt32("expire");	///< 订单超时时间
	_price_mode = cfg->getInt32("pricemode");	///< 价格模式，0-最新价，-1-对手价，1-排队价，2-自动，默认为0
	_entrust_span = cfg->getUInt32("span");		///< 下单时间间隔，单位毫秒
	_by_rate = cfg->getBoolean("byrate");		///< 是否按对手盘的挂单量的比例来下单，true时rate字段有效，false时lots字段有效
	_order_lots = cfg->getDouble("lots");		///< 单次下单手数
	_qty_rate = cfg->getDouble("rate");			///< 下单数量比例

	ctx->writeLog(fmt::sprintf("MiniImpactExecUnit %s inited, order price: %s ± %d ticks, order expired: %u secs, order timespan:%u millisec, order qty: %s @ %.2f",
		stdCode, PriceModeNames[_price_mode + 1], _price_offset, _expire_secs, _entrust_span, _by_rate ? "byrate" : "byvol", _by_rate ? _qty_rate : _order_lots).c_str());
}

/**
 * @brief 订单回报处理
 * @param localid 本地订单号
 * @param stdCode 合约代码
 * @param isBuy 买卖方向
 * @param leftover 剩余数量
 * @param price 委托价格
 * @param isCanceled 是否已撤销
 * @details 处理订单状态变化，更新订单监控器，触发重新计算
 */
void WtMinImpactExeUnit::on_order(uint32_t localid, const char* stdCode, bool isBuy, double leftover, double price, bool isCanceled)
{
	{
		if (!_orders_mon.has_order(localid))
			return;

		if (isCanceled || leftover == 0)
		{
			_orders_mon.erase_order(localid);
			if (_cancel_cnt > 0)
			{
				_cancel_cnt--;
				_ctx->writeLog(fmt::sprintf("[%s@%d] Order of %s cancelling done, cancelcnt -> %u", __FILE__, __LINE__, _code.c_str(), _cancel_cnt).c_str());
			}
		}

		if (leftover == 0 && !isCanceled)
			_cancel_times = 0;
	}

	// 如果是撤单回报，也要重新计算
	if (isCanceled)
	{
		_ctx->writeLog(fmt::sprintf("Order %u of %s canceled, recalc will be done", localid, stdCode).c_str());
		_cancel_times++;
		do_calc();
	}
}

/**
 * @brief 交易通道就绪回调
 * @details 处理通道就绪后的订单状态同步，清理无效订单
 */
void WtMinImpactExeUnit::on_channel_ready()
{
	double undone = _ctx->getUndoneQty(_code.c_str());

	if(!decimal::eq(undone, 0) && !_orders_mon.has_order())
	{
		/*
		 * 如果未完成的不为0，但是OMS没有订单
		 * 说明这些未完成的是在重启之前，全部撤销掉
		 * 因为这些订单没有被监控订单号，无法直接进行管理
		 * 这种情况一般是程序重启时，上次的未完成的订单是外部的挂单
		 */
		_ctx->writeLog(fmt::sprintf("Unmanaged live orders with qty %f of %s found, cancel all", undone, _code.c_str()).c_str());

		bool isBuy = (undone > 0);
		OrderIDs ids = _ctx->cancel(_code.c_str(), isBuy);
		_orders_mon.push_order(ids.data(), ids.size(), _ctx->getCurTime());
		_cancel_cnt += ids.size();

		_ctx->writeLog(fmt::sprintf("[%s@%d]cancelcnt -> %u", __FILE__, __LINE__, _cancel_cnt).c_str());
	}
	else if (decimal::eq(undone, 0) && _orders_mon.has_order())
	{
		/*
		 * By Wesley @ 2021.12.13
		 * 如果未完成的为0，但是OMS里面有订单
		 * 说明OMS里的订单可能要么被拒绝，要么已经时间过了，但是回调没有触发
		 * 这种情况一般是订单发出去以后，之前下出去的订单，并没有正确的传递到后台
		 * 所以这里需要清理掉监控订单
		 */
		_ctx->writeLog(fmt::sprintf("Local orders of %s not confirmed in trading channel, clear all", _code.c_str()).c_str());
		_orders_mon.clear_orders();
	}
	else
	{
		_ctx->writeLog(fmt::sprintf("Unrecognized condition while channle ready, %.2f live orders of %s exists, local orders %sexist",
			undone, _code.c_str(), _orders_mon.has_order() ? "" : "not ").c_str());
	}


	do_calc();
}

/**
 * @brief 交易通道丢失回调
 * @details 当前实现为空，可根据需要添加处理逻辑
 */
void WtMinImpactExeUnit::on_channel_lost()
{
	
}

/**
 * @brief tick数据回调处理
 * @param newTick 新的tick数据
 * @details 更新最新tick数据，检查订单超时，触发执行计算
 */
void WtMinImpactExeUnit::on_tick(WTSTickData* newTick)
{
	if (newTick == NULL || _code.compare(newTick->code()) != 0)
		return;

	// 先把原来的tick释放掉
	if (_last_tick)
	{
		_last_tick->release();
	}
	else
	{
		// 如果原来的时间不在交易时段，而且这一次是集合竞价的数据，下单会失败，所以直接过滤掉这些数据
		if (_sess_info != NULL && !_sess_info->isInTradingTime(newTick->actiontime() / 100000))
			return;
	}

	// 新的tick数据，要保留
	_last_tick = newTick;
	_last_tick->retain();

	/*
	 * 这里可以考虑一下
	 * 如果写的过于频繁，可能一次订单过去的时候并没有达到目标仓位
	 * 那么新的订单就会根据新数据来进行实时的重新触发执行逻辑
	 */

	if(_expire_secs != 0 && _orders_mon.has_order() && _cancel_cnt==0)
	{
		uint64_t now = _ctx->getCurTime();

		_orders_mon.check_orders(_expire_secs, now, [this](uint32_t localid) {
			if (_ctx->cancel(localid))
			{
				_cancel_cnt++;
				_ctx->writeLog(fmt::sprintf("[%s@%d] Expired order of %s canceled, cancelcnt -> %u", __FILE__, __LINE__, _code.c_str(), _cancel_cnt).c_str());
			}
		});
	}
	
	do_calc();
}

/**
 * @brief 成交回报处理
 * @param localid 本地订单号
 * @param stdCode 合约代码
 * @param isBuy 买卖方向
 * @param vol 成交数量
 * @param price 成交价格
 * @details 当前实现为空，成交处理在ontick里触发重新计算
 */
void WtMinImpactExeUnit::on_trade(uint32_t localid, const char* stdCode, bool isBuy, double vol, double price)
{
	// 这里什么都不做，统一在ontick里触发重新计算
}

/**
 * @brief 委托回报处理
 * @param localid 本地订单号
 * @param stdCode 合约代码
 * @param bSuccess 委托是否成功
 * @param message 回报消息
 * @details 处理委托失败的情况，从监控中移除失败订单
 */
void WtMinImpactExeUnit::on_entrust(uint32_t localid, const char* stdCode, bool bSuccess, const char* message)
{
	if (!bSuccess)
	{
		// 如果不是我发出去的订单，我就不管了
		if (!_orders_mon.has_order(localid))
			return;

		_orders_mon.erase_order(localid);

		do_calc();
	}
}

/**
 * @brief 执行计算主逻辑
 * @details 核心执行算法，包括：
 *          - 检查撤单状态和仓位差异
 *          - 计算下单数量和价格
 *          - 处理涨跌停限制
 *          - 执行买卖操作
 */
void WtMinImpactExeUnit::do_calc()
{
	if (_cancel_cnt != 0)
		return;

	// 这里加一个锁，主要是因为实盘过程中发现
	// 修改目标仓位的时候，会触发一次do_calc
	// 而ontick也会触发一次do_calc，两次调用是从不同线程触发的，可能会同时进来，造成问题
	// 如果不加锁，就会出现重复下单
	// 但是原来的SimpleExecUnit没有出现，是因为SimpleExecUnit只在set_position的时候触发
	StdUniqueLock lock(_mtx_calc);

	double newVol = get_real_target(_target_pos);
	const char* stdCode = _code.c_str();

	double undone = _ctx->getUndoneQty(stdCode);
	double realPos = _ctx->getPosition(stdCode);
	double diffPos = newVol - realPos;

	// 如果未完成订单和实际仓位变动方向相反
	// 说明需要先撤销订单
	if (decimal::lt(diffPos * undone, 0))
	{
		bool isBuy = decimal::gt(undone, 0);
		OrderIDs ids = _ctx->cancel(stdCode, isBuy);
		if(!ids.empty())
		{
			_orders_mon.push_order(ids.data(), ids.size(), _ctx->getCurTime());
			_cancel_cnt += ids.size();
			_ctx->writeLog(fmt::sprintf("[%s@%d] live opposite order of %s canceled, cancelcnt -> %u", __FILE__, __LINE__, _code.c_str(), _cancel_cnt).c_str());
		}
		return;
	}

	// 因为是逐步成交，所以如果有未完成的，就不需要继续下单了
	if (!decimal::eq(undone, 0))
		return;

	double curPos = realPos;


	if (_last_tick == NULL)
	{
		_ctx->writeLog(fmt::sprintf("No lastest tick data of %s, execute later", _code.c_str()).c_str());
		return;
	}

	// 检查下单时间间隔
	uint64_t now = TimeUtils::makeTime(_last_tick->actiondate(), _last_tick->actiontime());
	if (now - _last_place_time < _entrust_span)
		return;

	if (decimal::eq(curPos, newVol))
	{
		// 如果当前仓位和新仓位匹配时，要检查是否全部清仓，如果不是，直接退出就可以了
		if (!is_clear(_target_pos))
			return;

		// 如果是清仓指令，还要再进行对比
		// 如果多头为0，说明已经全部清理完了，就直接退出
		double lPos = _ctx->getPosition(stdCode, 1);
		if (decimal::eq(lPos, 0))
			return;

		// 如果还有多头仓位，目标仓位设置为负数，强制平仓
		newVol = -min(lPos, _order_lots);
		_ctx->writeLog(fmt::sprintf("Clearing process triggered, target position of %s has been set to %f", _code.c_str(), newVol).c_str());
	}

	bool bForceClose = is_clear(_target_pos);

	bool isBuy = decimal::gt(newVol, curPos);

	// 如果这次没有更新的tick，就不能下单，防止是之前的数据通过网络传输过来
	uint64_t curTickTime = (uint64_t)_last_tick->actiondate() * 1000000000 + _last_tick->actiontime();
	if (curTickTime <= _last_tick_time)
	{
		_ctx->writeLog(fmt::format("No tick of {} updated, {} <= {}, execute later",
			_code, curTickTime, _last_tick_time).c_str());
		return;
	}

	_last_tick_time = curTickTime;

	double this_qty = _order_lots;
	if (_by_rate)
	{
		this_qty = isBuy ? _last_tick->askqty(0) : _last_tick->bidqty(0);
		this_qty = round(this_qty*_qty_rate);
		if (decimal::lt(this_qty, 1))
			this_qty = 1;

		this_qty = min(this_qty, abs(newVol - curPos));
	}

	double buyPx, sellPx;
	if (_price_mode == 2)
	{
		double mp = (_last_tick->bidqty(0) - _last_tick->askqty(0))*1.0 / (_last_tick->bidqty(0) + _last_tick->askqty(0));
		bool isUp = (mp > 0);
		if (isUp)
		{
			buyPx = _last_tick->askprice(0);
			sellPx = _last_tick->askprice(0);
		}
		else
		{
			buyPx = _last_tick->bidprice(0);
			sellPx = _last_tick->bidprice(0);
		}

		/*
		 * By Wesley @ 2022.03.07
		 * 如果买卖价格为0，做一下处理
		 */
		if (decimal::eq(buyPx, 0.0))
			buyPx = decimal::eq(_last_tick->price(), 0.0) ? _last_tick->preclose() : _last_tick->price();

		if (decimal::eq(sellPx, 0.0))
			sellPx = decimal::eq(_last_tick->price(), 0.0) ? _last_tick->preclose() : _last_tick->price();

		buyPx += _comm_info->getPriceTick() * _cancel_times;
		sellPx -= _comm_info->getPriceTick() * _cancel_times;
	}
	else
	{
		if (_price_mode == -1)
		{
			buyPx = _last_tick->bidprice(0);
			sellPx = _last_tick->askprice(0);
		}
		else if (_price_mode == 0)
		{
			buyPx = _last_tick->price();
			sellPx = _last_tick->price();
		}
		else if (_price_mode == 1)
		{
			buyPx = _last_tick->askprice(0);
			sellPx = _last_tick->bidprice(0) - _comm_info->getPriceTick() * _price_offset;
		}

		/*
		 * By Wesley @ 2022.03.07
		 * 如果买卖价格为0，做一下处理
		 */
		if (decimal::eq(buyPx, 0.0))
			buyPx = decimal::eq(_last_tick->price(), 0.0)? _last_tick->preclose(): _last_tick->price();

		if (decimal::eq(sellPx, 0.0))
			sellPx = decimal::eq(_last_tick->price(), 0.0) ? _last_tick->preclose() : _last_tick->price();

		buyPx += _comm_info->getPriceTick() * _price_offset;
		sellPx -= _comm_info->getPriceTick() * _price_offset;
	}
	

	// 检查涨跌停板
	bool isCanCancel = true;
	if (!decimal::eq(_last_tick->upperlimit(), 0) && decimal::gt(buyPx, _last_tick->upperlimit()))
	{
		_ctx->writeLog(fmt::sprintf("Buy price %f of %s modified to upper limit price", buyPx, _code.c_str(), _last_tick->upperlimit()).c_str());
		buyPx = _last_tick->upperlimit();
		isCanCancel = false;	// 涨停价格不能设为可撤销，避免成交不了
	}
	
	if (!decimal::eq(_last_tick->lowerlimit(), 0) && decimal::lt(sellPx, _last_tick->lowerlimit()))
	{
		_ctx->writeLog(fmt::sprintf("Sell price %f of %s modified to lower limit price", sellPx, _code.c_str(), _last_tick->lowerlimit()).c_str());
		sellPx = _last_tick->lowerlimit();
		isCanCancel = false;	// 跌停价格不能设为可撤销，避免成交不了
	}

	if (isBuy)
	{
		OrderIDs ids = _ctx->buy(stdCode, buyPx, this_qty, bForceClose);
		_orders_mon.push_order(ids.data(), ids.size(), _ctx->getCurTime(), isCanCancel);
	}
	else
	{
		OrderIDs ids  = _ctx->sell(stdCode, sellPx, this_qty, bForceClose);
		_orders_mon.push_order(ids.data(), ids.size(), _ctx->getCurTime(), isCanCancel);
	}

	_last_place_time = now;
}

/**
 * @brief 设置目标仓位
 * @param stdCode 合约代码
 * @param newVol 新的目标仓位
 * @details 设置新的目标仓位，触发执行计算
 */
void WtMinImpactExeUnit::set_position(const char* stdCode, double newVol)
{
	if (_code.compare(stdCode) != 0)
		return;

	// 如果原来的目标仓位是DBL_MAX，说明已经进入清理逻辑
	// 这个时候如果新仓位为0，就直接跳过了
	if (is_clear(_target_pos) && decimal::eq(newVol, 0))
	{
		_ctx->writeLog(fmt::sprintf("%s is in clearing processing, position can not be set to 0", stdCode).c_str());
		return;
	}

	if (decimal::eq(_target_pos, newVol))
		return;

	_target_pos = newVol;

	if (is_clear(_target_pos))
		_ctx->writeLog(fmt::sprintf("%s is set to be in clearing processing", stdCode).c_str());
	else
		_ctx->writeLog(fmt::sprintf("Target position of %s is set tb be %f", stdCode, _target_pos).c_str());

	do_calc();
}

/**
 * @brief 清理全部仓位
 * @param stdCode 合约代码
 * @details 设置目标仓位为DBL_MAX，触发清仓逻辑
 */
void WtMinImpactExeUnit::clear_all_position(const char* stdCode)
{
	if (_code.compare(stdCode) != 0)
		return;

	_target_pos = DBL_MAX;

	do_calc();
}
